#region Copyright (c) 2003, newtelligence AG. All rights reserved.
/*
// Copyright (c) 2003, newtelligence AG. (http://www.newtelligence.com)
// Original BlogX Source Code: Copyright (c) 2003, Chris Anderson (http://simplegeek.com)
// All rights reserved.
//  
// Redistribution and use in source and binary forms, with or without modification, are permitted 
// provided that the following conditions are met: 
//  
// (1) Redistributions of source code must retain the above copyright notice, this list of 
// conditions and the following disclaimer. 
// (2) Redistributions in binary form must reproduce the above copyright notice, this list of 
// conditions and the following disclaimer in the documentation and/or other materials 
// provided with the distribution. 
// (3) Neither the name of the newtelligence AG nor the names of its contributors may be used 
// to endorse or promote products derived from this software without specific prior 
// written permission.
//      
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 
// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 
// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 
// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// -------------------------------------------------------------------------
//
// Original BlogX source code (c) 2003 by Chris Anderson (http://simplegeek.com)
// 
// newtelligence is a registered trademark of newtelligence Aktiengesellschaft.
// 
// For portions of this software, the some additional copyright notices may apply 
// which can either be found in the license.txt file included in the source distribution
// or following this notice. 
//
*/
#endregion


using System;
using System.Web.UI;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections;
using newtelligence.DasBlog.Runtime;

namespace newtelligence.DasBlog.Web.Core
{
	/// <summary>
	/// Summary description for BaseTemplateProcessor.
	/// </summary>
	public class TemplateProcessor
	{
		public TemplateProcessor()
		{                                                                  
			
		}

		public void ProcessTemplate(Page page, string templateString, Control contentPlaceHolder, Macros macros)
		{
			int lastIndex = 0;

            Regex templateFinder = new Regex("<(?<esc>\\$|%)\\s*(?<macro>(?!\\k<esc>).+?)\\s*\\k<esc>>");
						
			MatchCollection matches = templateFinder.Matches(templateString);
			foreach( Match match in matches )
			{
				if ( match.Index > lastIndex )
				{
					contentPlaceHolder.Controls.Add(new LiteralControl(templateString.Substring(lastIndex,match.Index-lastIndex)));
				}
				Group g = match.Groups["macro"];
				Capture c = g.Captures[0];
				Control ctrl = InvokeMacro(macros,c.Value) as Control;
				if (ctrl != null)
				{
					contentPlaceHolder.Controls.Add(ctrl);
				}
				else 
				{
					throw new ArgumentException("Macro",String.Format("Error executing macro: {0}. Make sure it you're calling it in your BlogTemplate with paratheses like 'myMacro()'. Macros with parameter lists and overloads must be called in this way.",c.Value));
				}
				lastIndex = match.Index+match.Length;
			}
			if ( lastIndex < templateString.Length)
			{
				contentPlaceHolder.Controls.Add(new LiteralControl(templateString.Substring(lastIndex,templateString.Length-lastIndex)));
			}
		}

		private bool IsMemberEligibleForMacroCall(MemberInfo m, object filterCriteria )
		{
			return String.Compare(m.Name,(string)filterCriteria,true)==0;
		}

        /// <summary>
        /// Gotta replace that with regex one day....
        /// </summary>
        /// <param name="args"></param>
        /// <param name="fromOffset"></param>
        /// <param name="endOffset"></param>
        /// <returns></returns>
		private string[] SplitArgs( string args, int fromOffset, ref int endOffset )
		{
			ArrayList arrList = new ArrayList();
			int argmark=fromOffset+1;
			bool indquote=false,insquote=false,inesc=false;
			char termChar = args[fromOffset]=='['?']':')';
			int argwalk;

			for( argwalk=fromOffset+1;argwalk<args.Length;argwalk++)
			{
				char chargwalk = args[argwalk];
				if ( char.IsWhiteSpace(chargwalk) && !indquote && !insquote )
				{
					continue;
				}
				if ( chargwalk == '\"' && !inesc )
				{
					indquote = !indquote;
					continue;
				}
				if ( chargwalk == '\'' && !inesc )
				{
					insquote = !insquote;
					continue;
				}
				if ( chargwalk == '\\' && (indquote || insquote) )
				{
					inesc=!inesc;
					continue;
				}
				if ( !indquote && !insquote && (chargwalk == ',' || chargwalk == termChar))
				{
					if ( argwalk > argmark )
					{
						string foundArg = args.Substring(argmark,argwalk-argmark).Trim();
                        if ( foundArg[0] == foundArg[foundArg.Length-1] )
                        {
                            foundArg = foundArg.Trim('\"','\'');
                        }
                        arrList.Add(foundArg);
						argmark = argwalk+1;
					}
					if ( chargwalk == termChar) 
					{
						break;
					}
				}
				inesc=false;
			}
			endOffset = argwalk+1;

			return (string[])arrList.ToArray(typeof(string));
		}

        /// <summary>
        /// This method invokes the macros. It's very tolerant about calls
        /// it can't make, meaning that it absorbs the exceptions
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="expression"></param>
        /// <returns></returns>
		private object InvokeMacro( object obj, string expression )
		{
            try
            {
                int subexStartIndex = 0;
                int subexEndIndex = 0;
                object subexObject = obj;
    			
                do
                {
                    subexEndIndex = expression.IndexOfAny(new char[]{'.','(','['}, subexStartIndex);
                    if ( subexEndIndex == -1 ) subexEndIndex=expression.Length;
                    string subex = expression.Substring(subexStartIndex,subexEndIndex-subexStartIndex);
                    subexStartIndex = subexEndIndex+1;
                    MemberInfo memberToInvoke;
                    MemberInfo[] members = subexObject.GetType().FindMembers(
                        MemberTypes.Field|MemberTypes.Method|MemberTypes.Property,
                        BindingFlags.IgnoreCase|BindingFlags.Instance|BindingFlags.Public,
                        new MemberFilter(this.IsMemberEligibleForMacroCall), subex.Trim() );
                    string[] arglist=null;

                    if ( members.Length == 0 )
                    {
                        throw new MissingMemberException(subexObject.GetType().FullName,subex.Trim());
                    }
                    if ( subexEndIndex<expression.Length && (expression[subexEndIndex] == '[' || expression[subexEndIndex] == '(') )
                    {
                        arglist = SplitArgs(expression,subexEndIndex, ref subexStartIndex);
                    }

                    memberToInvoke = null;
                    if ( members.Length >= 1 )
                    {
                        memberToInvoke = members[0];
                    }
                    else 
                    {
                        throw new InvalidOperationException(String.Format("Macro cannt be found for expression \"{0}\"", expression));
                    }


                    if ( memberToInvoke.MemberType == MemberTypes.Property &&
                        (subexEndIndex==expression.Length ||
                        expression[subexEndIndex] == '.' ||
                        expression[subexEndIndex] == '[' ))
                    {
                        PropertyInfo propInfo = memberToInvoke as PropertyInfo;
                        if ( subexEndIndex<expression.Length && expression[subexEndIndex] == '[' )
                        {
                            System.Reflection.ParameterInfo[] paramInfo = propInfo.GetIndexParameters();
                            if ( arglist.Length > paramInfo.Length )
                            {
                                throw new InvalidOperationException(String.Format("Parameter list length mismatch {0}",memberToInvoke.Name));
                            }
                            object[] oarglist = new object[paramInfo.Length];
                            for( int n=0;n<arglist.Length;n++)
                            {
                                oarglist[n] = Convert.ChangeType(arglist[n],paramInfo[n].ParameterType);
                            }
                            subexObject = propInfo.GetValue(subexObject,oarglist);
                        }
                        else
                        {
                            subexObject = propInfo.GetValue(subexObject,null);
                        }
                    }
                    else if ( memberToInvoke.MemberType == MemberTypes.Field &&
                        (subexEndIndex==expression.Length ||
                        expression[subexEndIndex] == '.'))
                    {
                        FieldInfo fieldInfo = memberToInvoke as FieldInfo;
                        subexObject = fieldInfo.GetValue(subexObject);
                    }
                    else if ( memberToInvoke.MemberType == MemberTypes.Method &&
                                subexEndIndex<expression.Length && expression[subexEndIndex] == '(' )
                    {
                        MethodInfo methInfo = memberToInvoke as MethodInfo;
                        System.Reflection.ParameterInfo[] paramInfo = methInfo.GetParameters();
                        if ( arglist.Length > paramInfo.Length && 
                            !(paramInfo.Length>0 && paramInfo[paramInfo.Length-1].ParameterType == typeof(string[])))
                        {
                            throw new InvalidOperationException(String.Format("Parameter list length mismatch {0}",memberToInvoke.Name));
                        }
                        object[] oarglist = new object[paramInfo.Length];
                        for( int n=0;n<arglist.Length;n++)
                        {
                            if ( n == paramInfo.Length-1 && 
                                arglist.Length>paramInfo.Length)
                            {
                                string[] paramsArg = new string[arglist.Length-paramInfo.Length+1];
                                for( int m=n;m<arglist.Length;m++)
                                {
                                    paramsArg[m-n] = Convert.ChangeType(arglist[n],typeof(string)) as string;
                                }
                                oarglist[n] = paramsArg;
                                break;
                            }
                            else
                            {
                                oarglist[n] = Convert.ChangeType(arglist[n],paramInfo[n].ParameterType);
                            }
                        }
                        subexObject = methInfo.Invoke(subexObject,oarglist);
                    }
                    
                }
                while(subexEndIndex<expression.Length && subexStartIndex < expression.Length);
                return subexObject==null?new LiteralControl(""):subexObject;
            }
            catch( Exception exc )
            {
                ErrorTrace.Trace(System.Diagnostics.TraceLevel.Error,exc);
            }
            return new LiteralControl("");
		}

	}
}
